﻿
#ifndef GameTimer_h
#define GameTimer_h

// CSharp includes.
#include <Typedef.h>
#include <Defines.h>

#include <REvents.h>
#include <System/Object.h>
#include <System/TimeSpan.h>
#include <System/IDisposable.h>
#include <System/Generic/EventHandler.h>
#include <System/EventArgs.h>
#include <System/Collections/Generic/List.h>
#include <System/Diagnostics/Stopwatch.h>

#include <Riccsson/Xna/Framework/GameTimerEventArgs.h>

namespace Riccsson
{
	namespace Xna
	{
		namespace Framework
		{
            // Not implemented.
			class InputEvents {};

            class GameTimer sealed
				: public Riccsson::System::Object
				, public Riccsson::System::IDisposable
            {
                PROP3(GameTimer, int, DrawOrder);
                PROP3(GameTimer, int, FrameActionOrder);
                PROP3(GameTimer, Riccsson::System::TimeSpan, UpdateInterval);
                PROP3(GameTimer, int, UpdateOrder);

                static GC_PTR<Riccsson::System::Collections::Generic::List<GC_PTR<GameTimer>>> _allTimers;

                static GC_PTR<Riccsson::System::Collections::Generic::List<GC_PTR<GameTimer>>> _frameTimers;
                static GC_PTR<Riccsson::System::Collections::Generic::List<GC_PTR<GameTimer>>> _updateTimers;
                static GC_PTR<Riccsson::System::Collections::Generic::List<GC_PTR<GameTimer>>> _drawTimers;

                static bool _resort;
        
                static int DefaultOrder;

                static Riccsson::System::TimeSpan DefaultUpdateInterval;
                                                                       
                static Riccsson::System::TimeSpan MaximumUpdateInterval;
                                                                       
                static Riccsson::System::TimeSpan VariableStepIncrement;

                
                private: bool _suppressDraw;

                private: bool _doDraw;
                private: Riccsson::System::TimeSpan _accumulatedElapsedTime;
                private: readonly GC_PTR<GameTimerEventArgs> _gameTime;

				private: static GC_PTR<Riccsson::System::Diagnostics::Stopwatch> _gameTimer;

                private: static GC_PTR<InputEvents> _windowEvents;

				COPY_CONSTRUCTOR(GameTimer)
				{
					PROP3_INIT_COPY(GameTimer, int, DrawOrder);
					PROP3_INIT_COPY(GameTimer, int, FrameActionOrder);
					PROP3_INIT_COPY(GameTimer, Riccsson::System::TimeSpan, UpdateInterval);
					PROP3_INIT_COPY(GameTimer, int, UpdateOrder);
				}

                public: PROP3_GET(int, DrawOrder)
				{
                    throw;
				}
                public: PROP3_SET(int, DrawOrder)
				{
                    throw;
				}

                public: PROP3_GET(int, FrameActionOrder)
			    {
                    throw;
			    }
                public: PROP3_SET(int, FrameActionOrder)
			    {
                    throw;
			    }

                public: PROP3_GET(Riccsson::System::TimeSpan, UpdateInterval)
			    {
                    throw;
			    }
                public: PROP3_SET(Riccsson::System::TimeSpan, UpdateInterval)
			    {
                    throw;
			    }

                public: PROP3_GET(int, UpdateOrder)
			    {
                    throw;
			    }
                public: PROP3_SET(int, UpdateOrder)
			    {
                    throw;
			    }

			    public: event<Void, Riccsson::System::Generic::EventHandler<Riccsson::System::EventArgs>> FrameAction;
                    
                public: event<Void, Riccsson::System::Generic::EventHandler<GameTimerEventArgs>> Draw;

                public: event<Void, Riccsson::System::Generic::EventHandler<GameTimerEventArgs>> Update;

                REGISTER_CLASS(GameTimer);

                public: GameTimer()
                    : PROP3_INIT(GameTimer, DrawOrder)
                    , PROP3_INIT(GameTimer, FrameActionOrder)
                    , PROP3_INIT(GameTimer, UpdateInterval)
                    , PROP3_INIT(GameTimer, UpdateOrder)
                    , _gameTime(new GameTimerEventArgs())
                {
                    _allTimers->Add(this);

                    

                    DrawOrder = DefaultOrder;
                    FrameActionOrder = DefaultOrder;
                    UpdateOrder = DefaultOrder;

                    UpdateInterval = DefaultUpdateInterval;
                }

                public: void Start()
                {
                    // Updates have to happen as they
                    // control if we draw or not.
                    if (!_updateTimers->Contains(this))
                        _updateTimers->Add(this);

                    // We only add the timers to the frame and draw
                    // lists if they actually have events to process.

                    if (FrameAction == null)
                        _frameTimers->Remove(this);
                    else if (!_frameTimers->Contains(this))
                        _frameTimers->Add(this);

                    if (Draw == null)
                        _drawTimers->Remove(this);
                    else if (!_drawTimers->Contains(this))
                        _drawTimers->Add(this);

                    // The lists need to be resorted now.
                    _resort = true;
                }

                public: void Stop()
                {
                    // Remove the timer from all the lists.
                    _frameTimers->Remove(this);
                    _updateTimers->Remove(this);
                    _drawTimers->Remove(this);
                }

                public: static void ResetElapsedTime()
                {
                    _gameTimer->Reset();
                    _gameTimer->Start();

                    foreach2 (var, timer, _allTimers)
                    {
                        (*timer)->_accumulatedElapsedTime = Riccsson::System::TimeSpan::Zero;
                        (*timer)->_gameTime->ElapsedTime = Riccsson::System::TimeSpan::Zero;
                    }
                }

                public: static void SuppressFrame()
                {
                    foreach2 (var, timer, _drawTimers)
                        (*timer)->_suppressDraw = true;
                }

                private: static void OnTick()
                {
                    //// Resort the timer update lists if they have been changed.
                    //if (_resort)
                    //{
                    //    _frameTimers->Sort((f, s) => f.FrameActionOrder - s.FrameActionOrder);
                    //    _drawTimers->Sort((f, s) => f.DrawOrder - s.DrawOrder);
                    //    _updateTimers->Sort((f, s) => f.UpdateOrder - s.UpdateOrder);
                    //    _resort = false;
                    //}
                    //
                    //// Do we need to initialize the window event handlers?
                    //if (_windowEvents == null && Window.Current != null)
                    //    _windowEvents = new InputEvents(Window.Current.CoreWindow, SharedGraphicsDeviceManager.Current.SwapChainPanel);
                    //if (_windowEvents != null)
                    //    _windowEvents.UpdateState();
                    //
                    //// First call all the frame events... we do this
                    //// every frame regardless of the elapsed time.
                    //foreach (var timer in _frameTimers)
                    //    timer.FrameAction(timer, EventArgs.Empty);
                    //
                    //// Next do update events.
                    //var elapsed = _gameTimer->Elapsed;
                    //_gameTimer->Reset();
                    //_gameTimer->Start();
                    //foreach (var timer : _updateTimers)
                    //    timer.DoUpdate(elapsed);
                    //
                    //// Timers that have been updated can now draw.
                    //var deviceManager = SharedGraphicsDeviceManager.Current;
                    //if (deviceManager != null && deviceManager.GraphicsDevice != null)
                    //{
                    //    // We gotta reapply the render targets on update
                    //    // for some reason...  i guess the OS changes them?
                    //    deviceManager.GraphicsDevice.ResetRenderTargets();
                    //
                    //    var doPresent = false;
                    //
                    //    foreach (var timer : _drawTimers)
                    //    {
                    //        if (!timer._doDraw)
                    //            continue;
                    //
                    //        timer.Draw(timer, timer._gameTime);
                    //
                    //        doPresent = true;
                    //        timer._doDraw = false;
                    //    }
                    //
                    //    // If nothing was drawn then we don't present and
                    //    // the swap chain will contain whatever was last rendered.
                    //    if (doPresent)
                    //        deviceManager.GraphicsDevice.Present();
                    //}
                    throw;
                }

                private: void DoUpdate(Riccsson::System::TimeSpan elapsed)
                {
                    //// Advance the accumulated elapsed time.
                    //_accumulatedElapsedTime += elapsed;
                    //
                    //// If not enough time has elapsed to perform an update we will 
                    //// try again on the next update call.
                    //if (    _accumulatedElapsedTime == Riccsson::System::TimeSpan::Zero || 
                    //        _accumulatedElapsedTime < UpdateInterval )
                    //    return;
                    //
                    //// Do not allow any update to take longer than our maximum.
                    //if (_accumulatedElapsedTime > MaximumUpdateInterval)
                    //    _accumulatedElapsedTime = MaximumUpdateInterval;
                    //
                    //// TODO: We should be calculating IsRunningSlowly
                    //// somewhere around here!
                    //
                    //// If we have an interval then we're working in fixed timestep mode.
                    //if (UpdateInterval > Riccsson::System::TimeSpan::Zero)
                    //{
                    //    _gameTime.ElapsedTime = UpdateInterval;
                    //    var stepCount = 0;
                    //
                    //    if (FrameAction != null)
                    //        FrameAction(this, null);
                    //
                    //    // Perform as many full fixed length time steps as we can.
                    //    while (_accumulatedElapsedTime >= UpdateInterval)
                    //    {
                    //        _gameTime.TotalTime += UpdateInterval;
                    //        _accumulatedElapsedTime -= UpdateInterval;
                    //        ++stepCount;
                    //
                    //        if (Update != null)
                    //            Update(this, _gameTime);
                    //    }
                    //
                    //    // Draw needs to know the total elapsed time
                    //    // that occured for the fixed length updates.
                    //    _gameTime.ElapsedTime = Riccsson::System::TimeSpan::FromTicks(UpdateInterval.get().Ticks * stepCount);
                    //}
                    //else
                    //{
                    //    // Perform a single variable length update.
                    //    _gameTime.ElapsedTime = _accumulatedElapsedTime;
                    //    _gameTime.TotalTime += _accumulatedElapsedTime;
                    //    _accumulatedElapsedTime = Riccsson::System::TimeSpan::Zero;
                    //
                    //    if (Update != null)
                    //        Update(this, _gameTime);
                    //}
                    //
                    //// Draw unless the update suppressed it.
                    //if (_suppressDraw)
                    //    _suppressDraw = false;
                    //else
                    //    _doDraw = true;
                    throw;
                }

                public: void Dispose()
                {
                    // Remove us from all timers.
                    _frameTimers->Remove(this);
                    _updateTimers->Remove(this);
                    _drawTimers->Remove(this);
                    _allTimers->Remove(this);
                }
            };
        }
	}
}

#endif